//
// Created by macos on 2021/12/9.
//

#include "mhttp.h"
#include "logger.h"

void init_string(struct http_response *s) {
    s->size = 0;
    s->response = malloc(s->size + 1);
    if (s->response == NULL) {
        fprintf(stderr, "malloc() failed\n");
        exit(EXIT_FAILURE);
    }
    memset(s->response, 0, s->size + 1);
}

size_t writefunc(void *data, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct http_response *mem = (struct http_response *) userp;

    char *ptr = realloc(mem->response, mem->size + realsize + 1);
    if (ptr == NULL)
        return 0;  /* out of memory! */

    mem->response = ptr;
    memcpy(&(mem->response[mem->size]), data, realsize);
    mem->size += realsize;
    mem->response[mem->size] = 0;

    return realsize;
}


uint16_t http_request(const char *url, HTTP_REQUEST_TYPE type, const char *content_type, mlist_t headers,
                      const char *request_body,
                      struct http_response *response) {
    struct curl_slist *curl_headers = NULL;
    curl_headers = curl_slist_append(curl_headers, "User-Agent: mhttp-client/1.0 Linux/x86_64");
    curl_headers = curl_slist_append(curl_headers, "Accept: */*");
    curl_headers = curl_slist_append(curl_headers, "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
    curl_headers = curl_slist_append(curl_headers, content_type);
    if (headers != NULL && list_size(headers) > 0) {
        for (int i = 0; i < list_size(headers); i++) {
            http_key_value_pair_t *kvp = (http_key_value_pair_t *) list_get(headers, i);
            size_t hlen = kvp->key_len + kvp->value_len + 3;
            char *header = (char *) malloc(hlen);
            memset(header, 0, hlen);
            strcpy(header, kvp->key);
            strcpy(header + kvp->key_len, ": ");
            strcpy(header + kvp->key_len + 2, kvp->value);
            curl_headers = curl_slist_append(curl_headers, header);
        }
    }
    //初始化curl
    CURL *curl = curl_easy_init();
    if (curl == NULL) {
        logError("libcurl初始化对象错误");
        curl_slist_free_all(curl_headers);
        return CURLE_FAILED_INIT;
    }
    struct http_response resp;
    init_string(&resp);
    //设置headers
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers);
    //设置请求url地址
    curl_easy_setopt(curl, CURLOPT_URL, url);
    //如果有重定向，则自动重定向跳转
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    //设置返回结果
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
    //执行curl
    if (request_body != NULL)
        logDebug("httpclient请求url:%s,参数:%s", url, request_body);
    else
        logDebug("httpclient请求url:%s,无参数", url);
    switch (type) {
        case HTTP_GET:
            curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
            break;
        case HTTP_POST:
            curl_easy_setopt(curl, CURLOPT_POST, 1);
            if (request_body != NULL && strlen(request_body) > 0)
                curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_body);
            break;
        default:
            break;
    }
    CURLcode ret = curl_easy_perform(curl);
    //判断执行结果
    if (ret != CURLE_OK) {
        logError("httpclient请求失败:%s", curl_easy_strerror(ret));
        return ret;
    }
    logDebug("httpclient返回结果:%s", resp.response);
    response->response = realloc(resp.response, resp.size + 1);
    response->size = resp.size;
    uint16_t http_status;
    curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &http_status);
    //释放内存
    curl_slist_free_all(curl_headers);
    curl_easy_cleanup(curl);
    return http_status;
}


uint16_t http_get(const char *url, mlist_t headers, mlist_t params, struct http_response *response) {
    char *url_get = (char *) url;
    if (params != NULL && list_size(params) > 0) {
        url_get = http_build_url_with_params((char *) url, params);
    }
    uint16_t http_status = http_request(url_get, HTTP_GET, "Content-Type: text/plain;charset=UTF-8", headers, NULL,
                                        response);
    return http_status;
}


uint16_t http_post(const char *url, mlist_t headers, mlist_t params, struct http_response *response) {
    char *post_body = "";
    if (params != NULL && list_size(params) > 0) {
        post_body = http_build_params(params);
    }
    uint16_t http_status = http_request(url, HTTP_POST, "Content-Type: application/x-www-form-urlencoded;charset=UTF-8",
                                        headers, post_body, response);
    return http_status;
}


uint16_t http_post_json(const char *url, mlist_t headers, json_doc *json, struct http_response *response) {
    char *post_body = "";
    if (json != NULL && json_doc_get_root(json) != NULL) {
        post_body = json_write(json, JSON_WRITE_NOFLAG, NULL);
    }
    uint16_t http_status = http_request(url, HTTP_POST, "Content-Type: application/json;charset=UTF-8", headers,
                                        post_body, response);
    return http_status;
}

uint16_t http_post_xml(const char *url, mlist_t headers, const char *xml, struct http_response *response) {
    uint16_t http_status = http_request(url, HTTP_POST, "Content-Type: application/xml;charset=UTF-8", headers, xml,
                                        response);
    return http_status;
}

void kv_put(void *kv, char *key, char *value) {
    http_key_value_pair_t *kvp = malloc(sizeof(http_key_value_pair_t *));
    kvp->key_len = strlen(key);
    kvp->value_len = strlen(value);
    strcpy(kvp->key, key);
    strcpy(kvp->value, value);
    list_add(kv, (void *) kvp, sizeof(*kvp));
}

char *kv_get(void *kv, char *key) {
    for (int i = 0; i < list_size((void *) kv); ++i) {
        http_key_value_pair_t *kvp = (http_key_value_pair_t *) list_get((void *) kv, i);
        if (strcmp(key, kvp->key) == 0)
            return kvp->value;
    }
    return "";
}


void kv_destory(void *kv) {
    list_free(kv);
}


/**
 * 生成urlencode编码的参数串，可用于post的body与get的query串
 * @param params 参数kv键值对
 * @return
 */
char *http_build_params(void *param) {
    char *query = (char *) malloc(8192);
    for (int i = 0; i < list_size(param); i++) {
        http_key_value_pair_t *kv = (http_key_value_pair_t *) list_get((void *) param, i);
        int key_len;
        int value_len;
        char *key_encoded = (char *) malloc(kv->key_len * 3);
        char *value_encoded = (char *) malloc(kv->value_len * 3);
        urlencode(kv->key, kv->key_len, key_encoded, &key_len);
        urlencode(kv->value, kv->value_len, value_encoded, &value_len);
        strcat(query, key_encoded);
        strcat(query, "=");
        strcat(query, value_encoded);
        if (i != list_size(param) - 1)
            strcat(query, "&");
    }
    return query;
}

/**
 * 用url基础串加参数键值对生成完整GET模式的url地址
 * @param url
 * @param params
 * @return
 */
char *http_build_url_with_params(char *url, http_key_value_pair_list param) {
    char *p = strchr(url, '?');
    char *query = http_build_params(param);
    char *new_url = (char *) malloc(strlen(query) + 1 + strlen(url));
    strcpy(new_url, url);
    if (p == NULL) {
        strcat(new_url, "?");
    } else {
        strcat(new_url, "&");
    }
    strcat(new_url, query);
    return new_url;
}

char *urlencode(const char *src, const int src_len, char *dest, int *dest_len) {
    static unsigned char hex_chars[] = "0123456789ABCDEF";
    const unsigned char *pSrc;
    const unsigned char *pEnd;
    char *pDest;

    pDest = dest;
    pEnd = (unsigned char *) src + src_len;
    for (pSrc = (unsigned char *) src; pSrc < pEnd; pSrc++) {
        if ((*pSrc >= '0' && *pSrc <= '9') ||
            (*pSrc >= 'a' && *pSrc <= 'z') ||
            (*pSrc >= 'A' && *pSrc <= 'Z') ||
            (*pSrc == '_' || *pSrc == '-' || *pSrc == '.')) {
            *pDest++ = *pSrc;
        } else if (*pSrc == ' ') {
            *pDest++ = '+';
        } else {
            *pDest++ = '%';
            *pDest++ = hex_chars[(*pSrc) >> 4];
            *pDest++ = hex_chars[(*pSrc) & 0x0F];
        }
    }

    *pDest = '\0';
    *dest_len = pDest - dest;

    return dest;
}

char *urldecode(const char *src, const int src_len, char *dest, int *dest_len) {
    (void) urldecode_ex(src, src_len, dest, dest_len);
    *(dest + *dest_len) = '\0';
    return dest;
}

char *urldecode_ex(const char *src, const int src_len, char *dest, int *dest_len) {
#define HEX_VALUE(ch, value) \
    if (ch >= '0' && ch <= '9') \
    { \
        value = ch - '0'; \
    } \
    else if (ch >= 'a' && ch <= 'f') \
    { \
        value = ch - 'a' + 10; \
    } \
    else \
    { \
        value = ch - 'A' + 10; \
    }

    const unsigned char *pSrc;
    const unsigned char *pEnd;
    char *pDest;
    unsigned char cHigh;
    unsigned char cLow;
    int valHigh;
    int valLow;

    pDest = dest;
    pSrc = (unsigned char *) src;
    pEnd = (unsigned char *) src + src_len;
    while (pSrc < pEnd) {
        if (*pSrc == '%' && pSrc + 2 < pEnd) {
            cHigh = *(pSrc + 1);
            cLow = *(pSrc + 2);

            if (IS_HEX_CHAR(cHigh) && IS_HEX_CHAR(cLow)) {
                HEX_VALUE(cHigh, valHigh)
                HEX_VALUE(cLow, valLow)
                *pDest++ = (valHigh << 4) | valLow;
                pSrc += 3;
            } else {
                *pDest++ = *pSrc;
                pSrc++;
            }
        } else if (*pSrc == '+') {
            *pDest++ = ' ';
            pSrc++;
        } else {
            *pDest++ = *pSrc;
            pSrc++;
        }
    }

    *dest_len = pDest - dest;
    return dest;
}
